#include "CMEIValidatorImpl.h"
#include <QSerialPort>
#include <iomanip>
#include <QTimerEvent>

#include "glog/logging.h"
#include "CommonType.h"

using namespace zl;

struct CMEIValidatorImpl::MEIImpl
{
    std::mutex mutex;
    QSerialPort serial;
    bool flag_poll;
};

CMEIValidatorImpl::CMEIValidatorImpl() :
    impl_(nullptr) ,
    timer_poll_(-1),
    flag_init_(false),
    status_(0)
{

}

CMEIValidatorImpl::~CMEIValidatorImpl()
{

}

int32_t CMEIValidatorImpl::Initialize(const std::string& com, int32_t baud, IValidatorObserver *obs)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "initialize mei validator: " << com << "@" << baud;

    if (impl_ != nullptr)
    {
        LOG(WARNING) << "validator is already initialized";
        return zl::EResult_Failed;
    }

    impl_ = new MEIImpl();
    impl_->serial.setPortName(com.c_str());
    impl_->serial.setBaudRate(baud);
    impl_->serial.setParity(QSerialPort::MarkParity);
    impl_->serial.setStopBits(QSerialPort::OneStop);
    auto ret = impl_->serial.open(QIODevice::ReadWrite);
    if (!ret)
    {
        LOG(ERROR) << "initialize mei validator failed, open serial port failed";
        delete impl_;
        impl_ = nullptr;
        return zl::EResult_Failed;
    }

    // start poll thread
    if (timer_poll_ != -1)
    {
        killTimer(timer_poll_);
    }
    timer_poll_ = startTimer(300);
    obs_ = obs;
    flag_init_ = true;
    LOG(INFO) << "initialize mei validator success";
    return zl::EResult_Success;
}

int32_t CMEIValidatorImpl::Uninitialize()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "uninitialize mei validator";

    if (impl_ == nullptr || !flag_init_)
    {
        LOG(WARNING) << "validator is not initialized";
        return zl::EResult_Failed;
    }

    // stop poll thread
    killTimer(timer_poll_);
    timer_poll_ = -1;
    impl_->serial.close();
    delete impl_;
    impl_ = nullptr;
    flag_init_ = false;
    return zl::EResult_Success;
}

int32_t CMEIValidatorImpl::Status()
{
    return status_;
}

int32_t CMEIValidatorImpl::Reset()
{
    LOG(INFO) << "MEI validator: RESET";
    if (!flag_init_)
    {
        return zl::EResult_Failed;
    }
    // send command reset
    mdb_cmd_reset();
    // send command poll
    mdb_cmd_poll();

    return zl::EResult_Success;
}

int32_t CMEIValidatorImpl::Setup()
{
    LOG(INFO) << "MEI validator: SETUP";
    if (!flag_init_)
    {
        return zl::EResult_Failed;
    }

    mdb_cmd_setup();

    return zl::EResult_Success;
}

int32_t CMEIValidatorImpl::Enable()
{
    LOG(INFO) << "MEI validator: ENABLE";
    if (!flag_init_)
    {
        return zl::EResult_Failed;
    }

    // send reset command
    mdb_cmd_reset();

    // send enable command
    auto ret = mdb_cmd_enable();
    if (ret != 1)
    {
        LOG(ERROR) << "enable validator failed: " << ret;
        return zl::EResult_Failed;
    }

    status_ = 1;
    impl_->flag_poll = true;
    return zl::EResult_Success;
}

int32_t CMEIValidatorImpl::Disable()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "MEI validator: DISABLE";
    if (!flag_init_)
    {
        return zl::EResult_Failed;
    }

    if (status_ == 0)
    {
        return zl::EResult_Success;
    }

    auto ret = mdb_cmd_disable();
    if (ret != 1)
    {
        LOG(ERROR) << "disable validator failed: " << ret;
        return zl::EResult_Failed;
    }

    status_ = 0;
    impl_->flag_poll = false;
    return zl::EResult_Success;
}

int32_t CMEIValidatorImpl::mdb_cmd_reset()
{
    std::unique_lock<std::mutex> locker(impl_->mutex);
    LOG(INFO) << "mdb reset session";

    // send request
    unsigned char cmd[2] = { 0x00 };
    cmd[0] = 0x30;
    cmd[1] = 0x30;
    auto cnt = write_mdb_buffer(cmd, 2);
    if (cnt != 2)
    {
        LOG(ERROR) << "mdb send reset command failed: " << cnt;
        return 0;
    }

    // recv response
    unsigned char buffer[128] = { 0x00 };
    size_t max_size = 128;
    auto ret = read_mdb_buffer(buffer, max_size);
    if (ret <= 0 || buffer[0] != 0x00)
    {
        LOG(ERROR) << "mdb command execute failed: " << std::hex << buffer;
        return 0;
    }

    LOG(INFO) << "mdb reset session done";
    return 1;
}

int32_t CMEIValidatorImpl::mdb_cmd_setup()
{
    std::unique_lock<std::mutex> locker(impl_->mutex);
    LOG(INFO) << "mdb setup session";

    // send request
    unsigned char cmd[2] = { 0x00 };
    cmd[0] = 0x31;
    cmd[1] = 0x31;
    auto cnt = write_mdb_buffer(cmd, 2);
    if (cnt != 2)
    {
        LOG(ERROR) << "mdb send command failed: " << cnt;
        return 0;
    }

    // recv response
    unsigned char buffer[128] = { 0x00 };
    size_t max_size = 128;
    auto ret = read_mdb_buffer(buffer, max_size);
    if (ret <= 0)
    {
        LOG(ERROR) << "mdb command execute failed: " << std::hex << buffer;
        return 0;
    }

    LOG(INFO) << "mdb setup session done";
    return 1;
}

int32_t CMEIValidatorImpl::mdb_cmd_poll()
{
    std::unique_lock<std::mutex> locker(impl_->mutex);
    // LOG(INFO) << "mdb poll session";

    // send request
    unsigned char cmd[2] = { 0x00 };
    cmd[0] = 0x33;
    cmd[1] = 0x33;
    auto cnt = write_mdb_buffer(cmd, 2);
    if (cnt != 2)
    {
        // LOG(ERROR) << "mdb send command poll: " << cnt;
        return 0;
    }

    // recv request
    unsigned char buffer[128] = { 0x00 };
    size_t max_size = 128;
    auto ret = read_mdb_buffer(buffer, max_size);
    if (ret <= 0)
    {
        // LOG(ERROR) << "mdb command execute failed: " << std::hex << buffer;
        return 0;
    }

    write_mdb_ack();
    auto pfunc_notify = [=](int32_t cnt, const std::string& t) -> void {
        if (this->obs_ == nullptr)
        {
            return;
        }

        obs_->EventCredit(cnt * 100, t);
    };

    // process poll response
    for (int i = 0; i < ret; ++i)
    {
        switch (buffer[i])
        {
        case 0x80:
            pfunc_notify(1, "CNY");
            break;
        case 0x81:
            pfunc_notify(5, "CNY");
            break;
        case 0x82:
            pfunc_notify(10, "CNY");
            break;
        case 0x83:
            pfunc_notify(20, "CNY");
            break;
        case 0x84:
            pfunc_notify(50, "CNY");
            break;
        default:
            break;
        }
    }

    return 1;
}

int32_t CMEIValidatorImpl::mdb_cmd_enable()
{
    std::unique_lock<std::mutex> locker(impl_->mutex);
    LOG(INFO) << "mdb enable session";

    // send request
    unsigned char cmd[6];
    cmd[0] = 0x34;
    cmd[1] = 0xFF;
    cmd[2] = 0xFF;
    cmd[3] = 0x00;
    cmd[4] = 0x00;
    cmd[5] = 0x32;
    auto cnt = write_mdb_buffer(cmd, 6);
    if (cnt != 6)
    {
        LOG(ERROR) << "mdb send command failed: " << cnt;
        return 0;
    }

    // recv response
    unsigned char buffer[128] = { 0x00 };
    size_t max_size = 128;
    auto ret = read_mdb_buffer(buffer, max_size);
    if (ret == 0)
    {
        LOG(ERROR) << "mdb command execute failed: " << std::hex << buffer;
        return 0;
    }

    LOG(INFO) << "mdb enable session done";
    return 1;
}

int32_t CMEIValidatorImpl::mdb_cmd_disable()
{
    std::unique_lock<std::mutex> locker(impl_->mutex);
    LOG(INFO) << "mdb disable session";

    // send request
    unsigned char cmd[6];
    cmd[0] = 0x34;
    cmd[1] = 0x00;
    cmd[2] = 0x00;
    cmd[3] = 0x00;
    cmd[4] = 0x00;
    cmd[5] = 0x34;
    auto cnt = write_mdb_buffer(cmd, 6);
    if (cnt != 6)
    {
        LOG(ERROR) << "mdb send command disable failed: " << cnt;
        return 0;
    }

    // recv response
    unsigned char buffer[128] = { 0x00 };
    size_t max_size= 128;
    auto ret = read_mdb_buffer(buffer, max_size);
    if (ret == 0)
    {
        LOG(ERROR) << "mdb command execute failed: " << std::hex << buffer;
        return 0;
    }

    LOG(INFO) << "mdb disable session done";
    return 1;
}

int32_t CMEIValidatorImpl::write_mdb_buffer(const unsigned char *buf, size_t size)
{
    if (impl_ == nullptr || size < 2 || buf == nullptr)
    {
        return 0;
    }

    // send with markparity
    size_t cnt(0);
    impl_->serial.setParity(QSerialPort::MarkParity);
    cnt = impl_->serial.write((const char*)buf, 1);
    if (cnt != 1)
    {
        return 0;
    }

    // send params
    impl_->serial.setParity(QSerialPort::SpaceParity);
    cnt = impl_->serial.write((const char*)buf + 1, size - 1);
    impl_->serial.flush();
    if (cnt != (size - 1))
    {
        return 0;
    }

    return size;
}

int32_t CMEIValidatorImpl::write_mdb_ack()
{
    if (impl_ == nullptr)
    {
        return 0;
    }

    impl_->serial.setParity(QSerialPort::SpaceParity);
    QByteArray cmd;
    cmd[0] = 0x00;
    cmd[1] = 0x00;
    auto cnt = impl_->serial.write(cmd);
    impl_->serial.flush();
    if (cnt != 2)
    {
        return 0;
    }

    return 1;
}

int32_t CMEIValidatorImpl::read_mdb_buffer(unsigned char *buf, size_t max_size)
{
    if (impl_ == nullptr || buf == nullptr || max_size <= 0)
    {
        return 0;
    }

    impl_->serial.setParity(QSerialPort::MarkParity);
    auto ret = impl_->serial.waitForReadyRead(500);
    if (!ret || !impl_->serial.isReadable())
    {
        return 0;
    }

    auto data = impl_->serial.readAll();
    impl_->serial.clear();
    auto max_cnt = data.size() > max_size ? max_size : data.size();
    memcpy(buf, data.data(), max_cnt);
    LOG(INFO) << "====== " << data.size() << " : " << data.toHex().toStdString();
    return max_cnt;
}

void CMEIValidatorImpl::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == timer_poll_)
    {
        if (impl_ != nullptr && impl_->flag_poll)
        {
            mdb_cmd_poll();
        }
    }

    return QObject::timerEvent(event);
}
